package test;

import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class LoadAllClasses {
	static final String CLASS_SUFFIX = ".class";

	public static void main(String[] args) {
		dumpProperty("sun.boot.class.path");
		dumpProperty("java.class.path");
		dumpProperty("aux.class.path");
		dumpProperty("path.separator");
		Set<File> roots = new LinkedHashSet<File>();
		toFiles(System.getProperty("sun.boot.class.path"), System.getProperty("path.separator"), roots);
		toFiles(System.getProperty("java.class.path"), System.getProperty("path.separator"), roots);
		toFiles(System.getProperty("aux.class.path"), System.getProperty("path.separator"), roots);
		System.out.println(roots.size());
		Set<String> classnames = new TreeSet<String>();
		scan(roots, classnames);
		System.out.println(classnames.size());
	}

	static void dumpProperty(String prop) {
		System.out.printf("%s = %s\n", prop, System.getProperty(prop));
	}

	static <C extends Collection<File>> C toFiles(String path, String sep, C collection) {
		for (String p : path.split(Pattern.quote(sep))) {
			collection.add(new File(p));
		}
		return collection;
	}

	static <C extends Collection<String>> C scan(Collection<File> roots, C classnames) {
		for (File root : roots) {
			scan(root, classnames);
		}
		return classnames;
	}

	static <C extends Collection<String>> C scan(File root, C classnames) {
		if (root.isFile()) {
			try {
				ZipFile zipfile = new ZipFile(root);
				try {
					Enumeration<? extends ZipEntry> entries = zipfile.entries();
					while (entries.hasMoreElements()) {
						ZipEntry entry = entries.nextElement();
						if (!entry.isDirectory()) {
							String path = entry.getName();
							if (path.endsWith(CLASS_SUFFIX)) {
								String classname = toClassName(path);
								classnames.add(classname);
							}
						}
					}
				} finally {
					zipfile.close();
				}
			} catch (IOException exc) {
				log(exc);
			}
		} else if (root.isDirectory()) {
			ArrayDeque<File> stack = new ArrayDeque<File>();
			stack.offerLast(root);
			File file;
			while ((file = stack.pollLast()) != null) {
				if (file.isDirectory()) {
					File[] children = file.listFiles();
					for (int i = children.length - 1; i >= 0; --i) {
						stack.offerLast(children[i]);
					}
				} else if (file.isFile()) {
					if (file.getName().endsWith(CLASS_SUFFIX)) {
						String classname = toClassName(getRelativePath(root, file));
						classnames.add(classname);
					}
				}
			}
		}
		return classnames;
	}

	static String toClassName(String relative) {
		return relative.substring(0, relative.length() - CLASS_SUFFIX.length()).replace('/', '.');
	}

	static String getRelativePath(File root, File file) {
		root = root.getAbsoluteFile();
		file = file.getAbsoluteFile();
		List<String> parts = new ArrayList<String>();
		for (; !root.equals(file); file = file.getParentFile()) {
			parts.add(file.getName());
		}
		assert !parts.isEmpty() : root + " " + file;
		int s = parts.size();
		StringBuilder builder = new StringBuilder(parts.get(s - 1));
		for (int i = s - 2; i >= 0; --i) {
			builder.append('/').append(parts.get(i));
		}
		return builder.toString();
	}

	static void log(Object msg) {
		System.out.println(msg);
	}
}
